#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <limits.h>
#include "logger2.h"

#define INFO 0
#define WARN 1
#define ERROR 2
#define NONE 3

//size of input file.

int size_of_file(char *INPUT){
    struct stat st;
    stat(INPUT, &st);
    int size = st.st_size;
    return size;
}

//check of correctness of format of input file. it must be CSV.

/*int correctness(char* input, int size){
    int i, r = 0;
    int num_of_arg = 1;
    for (i = 0; i < size; i++){
	if (input[i] == ','){
	    num_of_arg++;
	    if ((r == 0) && (i != 0)){
		r = 1;
	    }
	    else{
		return -1;
	    }
	}
	else{
	    if (input[i] == '-'){
		if ((i != 0) && (r != 1)){
		    return -1;
		}
		else{
		    r = 2;
		}
	    }
	    else{
		if ((input[i] <= '9') && (input[i] >= '0')){
		    r = 0;	  
		}
		else{
		    return -1;
		}
	    }
	}
    }
    if (r!=0){
	return -1;
    }
  
  //number can't begin with 0
  
    if (size > 1){
	if ((input[0] == '0') && (input[1] <= '9') && (input[1] >= '0')){
	    return -2;
	} 
    }
    if (size > 2){
	for (i = 0; i < size-2; i++){
	    if ((input[i] == ',') && (input[i+1] == '0') && (input[i+2] <= '9') && (input[i+2] >= '0')){
		return -2;
	    }
	}
    }
    return num_of_arg;
}*/

int correctness(char* input, int size){
    int numb = 0, comma = 0, dash = 0, danger = 0, num_of_arg = 1, i;
    for (i = 0; i < size; i++){
	if ((input[i] <= '9') && (input[i] >= '0')){
	    if (danger == 1){
		return -1;
	    }
	    if (input[i] == '0'){
		if (dash == 1){
		    return -6;
		}
		if (numb != 1){
		    danger = 1;
		}
	    }
	    numb = 1;
	    comma = 0;
	    dash = 0;
	}
	else if (input[i] == ','){
	    num_of_arg++;
	    if (numb != 1){
		return -2;
	    }
	    comma = 1;
	    numb = 0;
	    danger = 0;
	}
	else if(input[i] == '-'){
	    if ((comma != 1) && (i != 0)){
		return -3;
	    }
	    comma = 0;
	    numb = 0;
	    dash = 1;
	}
	else{
	    return -4;
	}
    }
    if (numb != 1){
	return -5;
    }
    return num_of_arg;
}


//mapping of input file and parsing arguments for sorting. return pointer on numbers, which will be sorted. the first element is number of arguments. 

void LOG_PRINT_ERROR(char* error_text){
    LOG_PRINT(ERROR, error_text);
    perror(error_text);
    exit(EXIT_FAILURE);
}	


int* parsed_arg(char *INPUT){
    char *map_input;      
    int fd = open(INPUT, O_RDONLY);
    if (fd == -1){
	LOG_PRINT_ERROR("opening of the input file for reading failed");
    }
    LOG_PRINT(INFO,"the input file was opened for reading");
    int size = size_of_file(INPUT);
    if (size == 0){
	close(fd);
	LOG_PRINT_ERROR("empty file");
    }  
    if (size >= INT_MAX){
	close(fd);
	LOG_PRINT_ERROR("size of file is very big");
    }  	
    map_input = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0);
    if (map_input == MAP_FAILED){
	close(fd);
	LOG_PRINT_ERROR("mapping of the input file failed");
    }  
    LOG_PRINT(INFO,"the input file was mapped");
    int num_of_arg = correctness(map_input, size);
//    printf("correctness_int = %d\n", correctness_int); 
    if (num_of_arg < 0){
	close(fd);
//	printf("num_of_arg = %d\n", num_of_arg);
	LOG_PRINT_ERROR("the input file isn't in CSV format");
    }     
    LOG_PRINT(INFO,"the content of the input file is correct");
//    printf("size = %d\n", size);
//    printf("num_of_arg = %d\n", num_of_arg);
    if (num_of_arg >= INT_MAX){
	close(fd);
	LOG_PRINT_ERROR("quantity of numbers in input file is very big");
    }
    LOG_PRINT(INFO,"quantity of numbers in input file is acceptable");

    int prev_ptr = 0, j = 0, i, k;
    char arg[12];
    int *M;    
    M = (int *)malloc((num_of_arg+1)*sizeof(int));
    
    if (num_of_arg != 1){	
    for (i = 0; i < size; i++){	
	if (map_input[i] == ','){
	    for (k = prev_ptr; k < i; k++){
		arg[k-prev_ptr] = map_input[k];
	    }
	    arg[i-prev_ptr] = '\0';
	    M[j+1] = strtol(arg, NULL, 10);
	    if ((M[j+1] >= INT_MAX) || (M[j+1] <= INT_MIN)){
		free(M);
		LOG_PRINT_ERROR("illegal size of number in the input file");
	    }
/*	    if ((num_of_arg - j) < 10){
	    	printf("%d \t[%d,%d] \t%d \t%s \t%d\n", j, prev_ptr, i-1, i-prev_ptr, arg, M[j+1]);
	    }*/
	    prev_ptr = i+1;
	    j++;
	}
    }
    }
    for (k = prev_ptr; k < size; k++){
	arg[k-prev_ptr] = map_input[k];
    }
    arg[size-prev_ptr] = '\0'; 
    M[j+1] = strtol(arg, NULL, 10);
    if ((M[j+1] >= INT_MAX) || (M[j+1] <= INT_MIN)){
	free(M);
	LOG_PRINT_ERROR("illegal size of number in the input file");
}
//    printf("%d \t[%d,%d] \t%d \t%s \t%d\n", j, prev_ptr, size-1, size-prev_ptr, arg, M[j+1]);
//    printf("size = %d \tnum_of_arg = %d\n", size, num_of_arg);
    
    M[0] = num_of_arg;
    LOG_PRINT(INFO,"the content of the input file was parsed for sorting");
    
    if (munmap(map_input, size) == -1){
	close(fd);
	LOG_PRINT_ERROR("unmapping of the input file failed");
    }
    LOG_PRINT(INFO,"the input file was unmapped");
    close(fd);
    return M;
}

//get sorted numbers and write them in mapped output file in CSV format.

void writing(int *M, int num_of_arg, char *OUTPUT, char *INPUT){
    char *map_output;
    int size = size_of_file(INPUT);
    int fd = open(OUTPUT, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600);
    if (fd == -1){
	LOG_PRINT_ERROR("opening of the output file for writing failed");
    }  
    LOG_PRINT(INFO,"the output file  was opened for writing");
    int end = lseek(fd, size-1, SEEK_SET);  
    if (end == -1){
	close(fd);
	LOG_PRINT_ERROR("stretching the output file failed");
    }  
    LOG_PRINT(INFO,"the output file was stretched");
    end = write(fd, "", 1); //stretching of output file  
    if (end != 1){
	close(fd);
	LOG_PRINT_ERROR("writing the last byte to the output file failed");
    }
    LOG_PRINT(INFO,"the last byte was written to the output file");
    
    map_output = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);  
    
    if (map_output == MAP_FAILED){
	close(fd);
	LOG_PRINT_ERROR("mapping the output file failed");
    } 
    LOG_PRINT(INFO,"the output file was mapped");
    
    char str[11]; 
    int i, j , num_of_digit, offset = 0;
    for (i = 1; i < num_of_arg+1; i++){	
/*	if ((i % 12500000) == 0){
	    printf("M[%d]=%d\n", i, M[i]);
	}*/
	num_of_digit = snprintf(str, 11, "%d", M[i]);
	for (j = 0; j < num_of_digit; j++){
	    map_output[j+offset] = str[j];
	}
	offset = offset + num_of_digit;	
	if (i == num_of_arg){
	    break;
	}
	map_output[offset] = ',';
	offset++;
    }
    LOG_PRINT(INFO,"writing in the output file was completed");
//    printf("%s\n", map_output);
//    LOG_PRINT("INFO: the content of the output file: %s", map_output);
    if (munmap(map_output, size) == -1){
	close(fd);
	LOG_PRINT_ERROR("unmapping the output file failed");
    }
    LOG_PRINT(INFO,"the output file was unmapped");
    close(fd);
}

int compare(const void *a, const void *b){
    return (*(int*)a-*(int*)b);
}


int main(int argc, char **argv){
    struct timeval tim; 
    gettimeofday(&tim, NULL);
    double t1=tim.tv_sec+(tim.tv_usec/1000000.0);
 
    SET_ERROR_LEVEL(NONE);
    
    char INPUT[50], OUTPUT[50];
    if ((argc == 2) && (strcmp(argv[1],"-h")) == 0){
	printf("\nHELP\n\nThe first command line argument is path of input file, the second - output file.\nFormat of input file is CSV.\nAll numbers in input file and their quantity must be integer\n\n");
	return 0;
    }
    else{  
	if (argc != 3){
	    printf("Call of help with command line argument '-h'\n");
	    return 0; 
	}
	else{
	    strcpy(INPUT, argv[1]);
	    strcpy(OUTPUT, argv[2]);
	}
    } 
    int *M = parsed_arg(INPUT);
    int num_of_arg = M[0];  
    
    qsort(M+1, num_of_arg, sizeof(int), compare);
    
    LOG_PRINT(INFO,"sorting completed");

    writing(M, num_of_arg, OUTPUT, INPUT);

    free(M); 
    LOG_PRINT(INFO,"the program successfully completed");
    gettimeofday(&tim, NULL);  
    double t2=tim.tv_sec+(tim.tv_usec/1000000.0);
    printf("%.6lf seconds elapsed\n", t2-t1);
    return 0;
}